package localcache

import (
	"encoding/json"
	"sync"
	"time"

	"github.com/dgraph-io/ristretto"
)

type LocalCache struct {
	mu        sync.RWMutex
	cache     *ristretto.Cache
	marshal   func(v interface{}) ([]byte, error)
	unmarshal func(data []byte, v interface{}) error
}

var Cache *LocalCache

// New 根据配置参数创建redis工具实例
func init() {
	c := new(LocalCache)
	cache, err := ristretto.NewCache(&ristretto.Config{
		NumCounters:        1e7,     // number of keys to track frequency of (10M).
		MaxCost:            1 << 30, // maximum cost of cache (1GB).
		BufferItems:        64,      // number of keys per Get buffer.
		IgnoreInternalCost: true,
	})
	if err != nil {
		panic(err)
	}
	c.cache = cache
	c.unmarshal = json.Unmarshal
	c.marshal = json.Marshal
	Cache = c
}

func (c *LocalCache) Wait() {
	c.cache.Wait()
}

func (c *LocalCache) Close() {
	c.cache.Close()
}

func (c *LocalCache) Set(key, value interface{}, cost int64) bool {
	defer c.Wait()
	return c.cache.Set(key, value, cost)
}

func (c *LocalCache) SetWithTTL(key, value interface{}, cost int64, ttl time.Duration) bool {
	defer c.Wait()
	return c.cache.SetWithTTL(key, value, cost, ttl)
}

func (c *LocalCache) SetNilWithTTL(key interface{}, cost int64, ttl time.Duration) bool {
	defer c.Wait()
	return c.cache.SetWithTTL(key, nil, cost, ttl)
}

func (c *LocalCache) Get(key interface{}) (interface{}, bool) {
	return c.cache.Get(key)
}

func (c *LocalCache) GetInt(key interface{}) (int, error) {
	value, ok := c.cache.Get(key)
	if !ok {
		return 0, Nil
	}
	return Int(value)
}

func (c *LocalCache) GetInt64(key interface{}) (int64, error) {
	value, ok := c.cache.Get(key)
	if !ok {
		return 0, Nil
	}
	return Int64(value)
}

func (c *LocalCache) GetFloat64(key interface{}) (float64, error) {
	value, ok := c.cache.Get(key)
	if !ok {
		return 0, Nil
	}
	return Float64(value)
}

func (c *LocalCache) GetString(key interface{}) (string, error) {
	value, ok := c.cache.Get(key)
	if !ok {
		return "", Nil
	}
	return String(value)
}

func (c *LocalCache) GetObject(key interface{}, val interface{}) error {
	value, ok := c.cache.Get(key)
	if !ok {
		return Nil
	}
	return c.Decode(value, val)
}

func (c *LocalCache) GetBool(key interface{}) (bool, error) {
	value, ok := c.cache.Get(key)
	if !ok {
		return false, Nil
	}
	return Bool(value)
}

func (c *LocalCache) GetTTL(key interface{}) (time.Duration, bool) {
	return c.cache.GetTTL(key)
}

func (c *LocalCache) Del(key interface{}) {
	c.cache.Del(key)
}

// decode 反序列化保存的struct对象
func (c *LocalCache) Decode(value interface{}, val interface{}) error {
	if value == nil {
		return ErrNil
	}
	v, err := c.marshal(value)
	if err != nil {
		return err
	}
	return c.unmarshal(v, val)
}

// Add an item to the cache only if an item doesn't already exist for the given
// key, or if the existing item has expired. Returns an error otherwise.
func (c *LocalCache) Add(key string, value interface{}, d time.Duration) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	_, found := c.Get(key)
	if !found {
		return Nil
	}
	c.SetWithTTL(key, value, 0, d)
	return nil
}
